#include "header.hpp"
using namespace std;

//thread pool
void test21(){

    class Task{
    public:
        Task(){}
        ~Task(){}
        virtual void process() = 0;
    };



    class TaskQueue{
        using ElemType = Task*;
    public:
        TaskQueue(int capa){
            _capacity = capa;
        }

        ~TaskQueue(){}

        void push(ElemType ptask){
            unique_lock<mutex> ul(_mutex);
            while(full()){
                _notFull.wait(ul);
            }
            _que.push(ptask);
            _notEmpty.notify_one();
        }

        ElemType pop(){
            unique_lock<mutex> ul(_mutex);

            while(empty() && _flag){
                _notEmpty.wait(ul);
            }

            if(_flag){
                ElemType tmp = _que.front();
                _que.pop();
                _notFull.notify_one();

                return tmp;
            } else {
                return nullptr;
            }

        }


        bool empty() const{
            return _que.size() == 0;
        }

        bool full() const{
            return _que.size() == _capacity;
        }

        void wakeUp(){
            _flag = false;
            _notEmpty.notify_all();
        }

    private:
        size_t _capacity;
        queue<ElemType> _que;
        mutex _mutex;
        condition_variable _notFull;
        condition_variable _notEmpty;
        bool _flag = true; //set false when wake up all threads
    };


    class ThreadPool{
    public:
        ThreadPool(size_t threadNum, size_t queSize)
        : _threadNum(threadNum)
        , _threads()
        , _queSize(queSize)
        , _taskQue(_queSize)
        , _isExit(false)
        {}

        ~ThreadPool(){}

        void start(){
            for(size_t i = 0; i != _threadNum; ++i){
                //thread t(&ThreadPool::doTask, this);
                //_threads.push_back(move(t));
                _threads.push_back(thread(&ThreadPool::doTask, this));
            }
        }

        void stop(){
            while(!_taskQue.empty()){
                //give up the rights of control of CPU
                this_thread::sleep_for(chrono::seconds(1));
            }

            _isExit = true;
            _taskQue.wakeUp();

            for(auto& t : _threads){
                t.join();
            }
        }

        void addTask(Task* ptask){
            if(ptask){
                _taskQue.push(ptask);
            }
        }

    private:
        Task* getTask(){
            return _taskQue.pop();
        }

        void doTask(){
            while(!_isExit){
                Task* ptask = getTask();

                if(ptask){
                    //execute the task by polymorphism
                    ptask->process();

                    //this_thread::sleep_for(chrono::seconds(2));

                } else {
                    cout << "ptask == nullptr" << endl;
                }
            }
        }


    private:
        size_t _threadNum;
        vector<thread> _threads;

        size_t _queSize;
        TaskQueue _taskQue;

        bool _isExit;
    };


    //Test instance
    class MyTask : public Task{
    public:
        void process() override{
            srand(time(NULL));
            int num = rand() % 100;

            cout << ">>MyTask process: num = " << num << endl;
        }
    };


    unique_ptr<Task> ptask(new MyTask());

    ThreadPool pool(4, 10);
    pool.start();

    int count = 20;
    while(count--){
        pool.addTask(ptask.get());
    }

    pool.stop();
}
